package org.halk.redis.lock.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.halk.redis.lock.annotation.RepeatSubmit;
import org.halk.redis.lock.exception.CustomException;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * 防止重复提交切面
 *
 * @Author halk
 * @Date 2021/12/23 17:58
 * @desc
 */
@Aspect
@Slf4j
@Component
@RequiredArgsConstructor
public class RepeatSubmitAspect {

    private final RedissonClient redissonClient;


    //切面织入点配置可以有多种方式


    /**
     * 根据token、请求路径、参数判断是否是同一个请求，进而判定是否是短时间内的重复请求
     *
     * @param point
     * @param repeatSubmit
     */
//    @Before("@annotation(org.halk.redis.lock.annotation.RepeatSubmit)")
    @Before("@annotation(repeatSubmit)")
    @SneakyThrows
    public void around(JoinPoint point, RepeatSubmit repeatSubmit) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        String token = request.getHeader("token");
        String servletPath = request.getServletPath();

        //对参数进行序列化
        Object[] args = point.getArgs();
        ObjectMapper objectMapper = new ObjectMapper();
        String argsJson = objectMapper.writeValueAsString(args);

        //对参数序列化
        String allParams = token + servletPath + argsJson;

        //计算md5值，多种方式
        String md5 = DigestUtils.md5DigestAsHex(allParams.getBytes(StandardCharsets.UTF_8));
//        String md5 = MD5.create().digestHex(allParams);

        //获取锁，并尝试获取锁
        RLock lock = redissonClient.getLock(md5);
        boolean flag = lock.tryLock(1000, 6000, TimeUnit.MILLISECONDS);

        if (flag) {
            log.info("获取锁成功，资源被锁定6秒钟  key=[{}]", md5);
        } else {
            log.error("接口短时间重复调用！歇会再试吧 key =[{}]", md5);
            throw new CustomException("接口短时间重复调用！歇会再试吧", HttpStatus.BAD_REQUEST);
        }
    }

}
